
import EventEmitter from "events";
import { Bid, OrderBook, Depth } from "./OrderBook";
import { PeriodProcess, Period } from "./PeriodProcess";
import { throttle } from "lodash";
import { keepDecimalFixed } from "../utils/index";

// 统一处理订阅symbol的candlesticks、trades、depth数据
export interface CandleStick {
  symbol: string;
  open: number;
  high: number;
  low: number;
  close: number;
  volume?: number;
  startTime: number;
}
export interface Trade {
  symbol: string;
  quantity: number;
  direction: "BUY" | "SELL";
  eventTime: number;
  price: number;
}


export interface MergeData {
  symbol: string;
  open: number;
  high: number;
  low: number;
  close: number;
  volume: number;
  timestamp: number;
  time?: Date;
  buy: number;
  sell: number;
  asks?: Bid[];
  bids?: Bid[];
  isMock?: boolean;
  label?: number;
}


export class DataHandler {
  private periodProcessMap: Record<string, PeriodProcess<MergeData>>;
  private eventEmitter = new EventEmitter();
  private dataMap: Record<string, MergeData>;
  type: 'mock' | 'real'
  orderBook: OrderBook;
  futruesOrderBook: OrderBook;
  constructor(type: 'mock' | 'real' = 'real') {
    this.type = type;
    this.periodProcessMap = {};
    this.dataMap = {};
    this.orderBook = new OrderBook();
    this.futruesOrderBook = new OrderBook();
  }


  private initPeriodProcess(symbol: string) {
    if (this.periodProcessMap[symbol]) {
      return;
    }
    const filterDepth = (item: Bid) => {
      const currentPrice = this.dataMap[`${symbol}-15m`].close;
      // 过滤价格超过当前价格 20% 的数据
      if (item.price > currentPrice * 1.3 || item.price < currentPrice * 0.7) {
        return false;
      }
      return true;
    }
    const periods = ['15m', '4h', '1d'] as Period[];
    this.periodProcessMap[symbol] = new PeriodProcess(periods);
    // 初始化数据
    periods.forEach(item => {
      this.dataMap[`${symbol}-${item}`] = {} as any
    })
    this.periodProcessMap[symbol].onPeriod((period, data) => {
      const key = `${symbol}-${period}`;
      if (data) {
        this.dataMap[key] = data;
        const depthData = this.orderBook.getBook(symbol);
        this.dataMap[key].asks = depthData.asks.filter(filterDepth);
        this.dataMap[key].bids = depthData.bids.filter(filterDepth);
        this.eventEmitter.emit(`interval:${period}`, this.dataMap[key]);
      }
    });
    if (this.type === 'real') {   
      const periodThrottle = periods.reduce((pre, cur) => {
        pre[cur] = throttle((period, data) => {
          const depthData = this.orderBook.getBook(symbol);
          this.eventEmitter.emit(`update:${period}`, Object.assign(data, {
            symbol,
            bids: depthData.bids.filter(filterDepth),
            asks: depthData.asks.filter(filterDepth),
          }));
        }, 2000, {leading: true});
        return pre;
      }, {} as Record<Period, (period: Period, data: MergeData) => void>)
      this.periodProcessMap[symbol].onUpdate((period, data) => {
        if (data !== undefined) {
          periodThrottle[period](period, data);
        }
      });
    } else {
      this.periodProcessMap[symbol].onUpdate((period, data) => {
        if (data) {
          // const depthData = this.orderBook.getBook(symbol);
          this.eventEmitter.emit(`update:${period}`, Object.assign(data, {
            symbol,
            bids: [],
            asks: [],
          }));
        }
      });
    }
  }

  /**
   * @param quote BTC ETH USDT
   */
  public getQuotePrice(quote: string) {
    if (quote === 'USDT') {
      return 1;
    }
    return this.dataMap[`${quote.toUpperCase()}USDT-15m`] ? this.dataMap[`${quote.toUpperCase()}USDT-15m`].close : 0;
  }
  public on(event:'interval:5m' | 'interval:15m' | 'interval:4h' | 'interval:1d' | 'update:15m' | 'update:4h' | 'update:1d' , callback: (data: MergeData) => void) {
    this.eventEmitter.on(event, callback);
  }
  public handleHistory = (data: Omit<MergeData, 'asks' | 'bids' | 'timestamp'> & {timestamp?: number}) => {
    if (this.periodProcessMap[data.symbol] == undefined) {
      this.initPeriodProcess(data.symbol);
    }
    if (data.timestamp === undefined && data.time) {
      data.timestamp = new Date(data.time).getTime();
    }
    this.periodProcessMap[data.symbol].process(data.timestamp, (mergeData) => {
      // 不要覆盖时间
      mergeData.symbol = data.symbol;
      mergeData.close = Number(data.close);
      mergeData.high =  mergeData.high == undefined ? data.high : Math.max(mergeData.high, data.high);
      mergeData.low = mergeData.low == undefined ? data.low : Math.min(mergeData.low, data.low);
      mergeData.volume = mergeData.volume === undefined ? data.volume : mergeData.volume + data.volume;
      if (mergeData.open === undefined) {
        mergeData.open = data.open
        mergeData.isMock = true;
      }
    });

    this.periodProcessMap[data.symbol].process(data.timestamp, (mergeData) => {
      // 不要覆盖时间
      mergeData.symbol = data.symbol;
      mergeData.buy = mergeData.buy === undefined ?  data.buy : keepDecimalFixed(mergeData.buy + data.buy, 3);
      mergeData.sell = mergeData.sell === undefined ?  data.sell : keepDecimalFixed(mergeData.sell + data.sell, 3);
      mergeData.isMock = true;
    });
  }
  public handleCandlesticks = (data: CandleStick) => {
    if (this.periodProcessMap[data.symbol] == undefined) {
      this.initPeriodProcess(data.symbol);
    }
    this.periodProcessMap[data.symbol].process(data.startTime, (mergeData) => {
      mergeData.symbol = data.symbol;
      mergeData.close = data.close;
      mergeData.high =  mergeData.high == undefined ? data.high : Math.max(mergeData.high, data.high);
      mergeData.low = mergeData.low == undefined ? data.low : Math.min(mergeData.low, data.low);
      if (mergeData.open == undefined) {
        mergeData.open = data.open;
      }
      mergeData.volume = data.volume;
    });
  }

  public handleTrades = (data: Trade[]) => {
    if (!data.length) {
      return;
    }
    const symbol = data[0].symbol;
    let sell = 0;
    let buy = 0;
    data.forEach((item) => {
      if (item.direction === 'BUY') {
        buy += item.quantity;
      } else {
        sell += item.quantity;
      }
    });
    if (this.periodProcessMap[symbol] == undefined) {
      this.initPeriodProcess(symbol);
    }
    this.periodProcessMap[symbol].process(data[0].eventTime, (mergeData) => {
      mergeData.buy = mergeData.buy === undefined ?  buy : mergeData.buy + buy;
      mergeData.sell = mergeData.sell === undefined ?  sell : mergeData.sell + sell;
      // 备用
      if (mergeData.close === undefined && data[0].price) {
        mergeData.close = data[0].price
        mergeData.open = data[0].price
        mergeData.low = data[0].price
        mergeData.high = data[0].price
      }
    });
  }

  public handleDepth(data: Depth) {
    this.orderBook.process(data)
  }
  public handleFutruesDepth(data: Depth) {
    this.futruesOrderBook.process(data)
  }
}

